package com.tools.web.interceptor.access.spel;

import com.tools.common.exception.service.ServiceException;
import com.tools.common.object.Note;
import com.tools.web.GlobalKit;
import com.tools.web.interceptor.access.AccessManager;
import com.tools.web.interceptor.access.AccessSource;
import com.tools.web.interceptor.access.OwnCron;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.resource.ResourceHttpRequestHandler;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Objects;

/**
 * 接口访问的授权检查拦截器
 * */
@Note("接口访问的授权检查拦截器")
public final class AuthorizeManager implements AccessManager {

    @Note("没有角色/权限访问时返回的错误信息，如果为空则会抛 ServiceException")
    private final String accessFailResponseMessage;

    @Note("若开启了时间范围验证，且接口的开放时间还没到，就响应该错误信息。如果为空则会抛 ServiceException")
    private final String unreleasedResponseMessage;

    @Note("基础授权检查功能的处理器对象")
    private final AuthorizeHandler authorizeHandler;

    @Note("Spring 的 SpEL 语法解析器")
    private static final ExpressionParser PARSER = new SpelExpressionParser();

    @Note("授权检查处理器在上下文中的名称")
    public static final String AH = "AH";

    @Note("当前请求对象在上下文中的名称")
    public static final String REQ = "REQ";

    @Note("当前用户认证对象在上下文中的名称")
    public static final String AUTH = "AU";

    /* *****************************************************************************************
     *
     *      构造器
     *
     * *****************************************************************************************
     * */

    public AuthorizeManager(AccessSource source) {
        this(source, new AuthorizeHandler());
    }

    public AuthorizeManager(AccessSource source, AuthorizeHandler authorizeHandler) {
        if(source == null) throw new NullPointerException("权限验证拦截器所需的参数配置项包装类不能为 null");
        this.authorizeHandler = Objects.requireNonNull(authorizeHandler);
        this.accessFailResponseMessage = source.getAccessFailResponseMessage();
        this.unreleasedResponseMessage = source.getUnreleasedResponseMessage();
    }

    /* *****************************************************************************************
     *
     *      拦截逻辑
     *
     * *****************************************************************************************
     * */

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        return this.preAuthorize(request, response, handler);
    }

    @Override
    public boolean preAuthorize(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        if(handler instanceof ResourceHttpRequestHandler) return true;
        HandlerMethod handlerMethod = (HandlerMethod) handler;
        AccessSPEL methodAno = handlerMethod.getMethodAnnotation(AccessSPEL.class);
        if(methodAno != null) {
            return this.private_accessCheck(methodAno, request, response);
        }
        Class<?> beanType = handlerMethod.getBeanType();
        AccessSPEL classAno = beanType.getAnnotation(AccessSPEL.class);
        return classAno == null || this.private_accessCheck(classAno, request, response);
    }

    /* *****************************************************************************************
     *
     *      私有逻辑
     *
     * *****************************************************************************************
     * */

    @Note("核心校验逻辑")
    private boolean private_accessCheck(AccessSPEL a, HttpServletRequest request,
                                        HttpServletResponse response) throws Exception {
        if(!this.private_checkIsOpenDateTime(a)) {
            return this.private_handleUnreleasedCheckResult(response);
        }
        String spel = a.value();
        StandardEvaluationContext context = new StandardEvaluationContext();
        context.setVariable(AH, this.authorizeHandler);
        context.setVariable(REQ, request);
        context.setVariable(AUTH, GlobalKit.getAuthUser(request));
        Boolean result = PARSER.parseExpression(spel).getValue(context, Boolean.class);
        if(result != null && result) return true;
        return this.private_handleAccessFailCheckResult(response);
    }

    @Note("校验是否开启时间验证，若开启则当前时间是否大于等于接口开放的时间，且小于接口关闭的时间。" +
            "若接口关闭时间没有声明，则接口开放后一直有效了")
    private boolean private_checkIsOpenDateTime(AccessSPEL a) {
        String startCron = a.start();
        String endCron = a.end();
        if(!startCron.isEmpty() && !new OwnCron(startCron).gteNow()) {
            return false;
        }
        if(endCron.isEmpty()) return true;
        return new OwnCron(endCron).ltNow();
    }

    @Note("处理结果，若为 false 则返回错误信息并阻止继续向下执行")
    private boolean private_handleAccessFailCheckResult(HttpServletResponse response) throws Exception {
        if(this.accessFailResponseMessage != null && !this.accessFailResponseMessage.isEmpty()) {
            response.getWriter().write(this.accessFailResponseMessage);
            return false;
        }
        throw new ServiceException("访问失败，您没有相应的角色或权限");
    }

    @Note("处理结果，若为 false 则返回错误信息并阻止继续向下执行")
    private boolean private_handleUnreleasedCheckResult(HttpServletResponse response) throws Exception {
        if(this.unreleasedResponseMessage != null && !this.unreleasedResponseMessage.isEmpty()) {
            response.getWriter().write(this.unreleasedResponseMessage);
            return false;
        }
        throw new ServiceException("访问失败，该服务尚未开放访问");
    }
}
